﻿//   AppStorageManager.cs
//   Copyright (c) 2012 Eswar Prakash
// 
//    This library is free software; you can redistribute it and/or modify  
//    it  under the terms of the GNU Lesser General Public License version  
//    2.1 as published by the Free Software Foundation.                     
//                                                                          
//    This library is distributed in the hope that it will be useful, but   
//    WITHOUT ANY WARRANTY; without even the implied warranty of            
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     
//    Lesser General Public License for more details.                       
//                                                                          
//    You should have received a copy of the GNU Lesser General Public      
//    License along with this library; if not, write to the Free Software   
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  
//    USA
namespace CKSAS.AppStorage
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.SharePoint;
    using SharedService;
    using System.Data;
    using Microsoft.SharePoint.Administration;
    using CKSAS.Core.Utilities;


    /// <summary>
    /// Provides a proxy for accessing and modifying storage information for an application
    /// </summary>
    public sealed class AppStorageManager
    {
        /// <summary>
        /// Private member variable that is used to hold the site context for this
        /// instance
        /// </summary>
        private object contextObject = null;

        /// <summary>
        /// Private member variable that is used to hold the partition context of the
        /// storage
        /// </summary>
        private PartitionContextTypes partitionContext = PartitionContextTypes.WebApplication;

        /// <summary>
        /// Initializes a new instance of the <see cref="AppStorageManager"/> class.
        /// </summary>
        /// <param name="site">The site which will be used as a context to connect to the service application.</param>
        public AppStorageManager(SPSite site)
        {
            this.contextObject = site;
            this.partitionContext = PartitionContextTypes.SiteCollection;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AppStorageManager"/> class.
        /// </summary>
        /// <param name="web">The web whichh will be used as a context to connect to the service application.</param>
        public AppStorageManager(SPWeb web)
        {
            this.contextObject = web;
            this.partitionContext = PartitionContextTypes.Site;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AppStorageManager"/> class.
        /// </summary>
        /// <param name="list">The list which will be used a context to connect to the service application.</param>
        public AppStorageManager(SPList list)
        {
            this.contextObject = list;
            this.partitionContext = PartitionContextTypes.List;
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="AppStorageManager"/> class.
        /// </summary>
        /// <param name="webApplication">The web application that will be used a context to connect to the service application.</param>
        public AppStorageManager(SPWebApplication webApplication)
        {
            this.contextObject = webApplication;
            this.partitionContext = PartitionContextTypes.WebApplication;
        }

        /// <summary>
        /// Gets an instance of a stored object of the specified type
        /// </summary>
        /// <typeparam name="T">The type of the stored object. The type should be a serializable type</typeparam>
        /// <param name="name">The name of the object in the storage.</param>
        /// <returns>
        /// An instance of <typeparamref name="T"/>
        /// </returns>
        public T GetObject<T>(string name) where T : StoredObject
        {
            var serviceContext = this.ServiceContext;
            if (serviceContext == null)
            {
                throw new SPException("Cannot get an instance of the service context associated with the provide site");
            }

            var proxy = AppStorageServiceApplicationProxy.GetProxy(serviceContext);
            var storageItem = proxy.GetStorageItem(this.Partition, name);
            if (storageItem != null)
            {
                var obj = BinarySerializer.Deserialize<T>(storageItem.Data);
                obj.Manager = this;
                obj.Status = StoredObjectStatus.Online;
                obj.CreatedBy = storageItem.CreatedBy;
                obj.CreatedDate = storageItem.CreatedDate;
                obj.ModifiedBy = storageItem.ModifiedBy;
                obj.ModifiedDate = storageItem.ModifiedDate;

                return obj;
            }

            return default(T);
        }

        /// <summary>
        /// Saves a new object or updates the value of an existing object of the specified type in the storage
        /// </summary>
        /// <typeparam name="T">The type of the stored object. The type should be inherited from <see cref="StoredObject"/></typeparam>
        /// <param name="obj">The object to store in the storage.</param>
        internal void SetObject<T>(T obj) where T : StoredObject
        {
            var serviceContext = this.ServiceContext;
            if (serviceContext == null)
            {
                throw new SPException("Cannot get an instance of the service context associated with the provide site");
            }

            var proxy = AppStorageServiceApplicationProxy.GetProxy(serviceContext);
            var storageItem = proxy.GetStorageItem(this.Partition, obj.Name);
            var currentUserLoginName = SPContext.Current.Web.CurrentUser.LoginName;
            if (storageItem != null)
            {
                // is it a duplicate? check the object type
                if (storageItem.TypeFullName != obj.GetType().AssemblyQualifiedName)
                {
                    throw new DuplicateNameException("The provided name is already in use by another object");
                }

                // update the value in the storage, but first serialize the object to bytes
                storageItem.Data = BinarySerializer.Serialize(obj);
                storageItem.ModifiedBy = currentUserLoginName;
                proxy.UpdateStorageItem(this.Partition, obj.Name, storageItem);
                obj.Status = StoredObjectStatus.Online;
                obj.CreatedBy = storageItem.CreatedBy;
                obj.CreatedDate = storageItem.CreatedDate;
                obj.ModifiedBy = storageItem.ModifiedBy;
                obj.ModifiedDate = storageItem.ModifiedDate;
            }
            else
            {
                // create the object now
                storageItem = StorageItem.CreateFromObject(obj);
                storageItem.CreatedBy = currentUserLoginName;
                proxy.AddStorageItem(this.Partition, obj.Name, storageItem);
                obj.Status = StoredObjectStatus.Online;
                obj.CreatedBy = storageItem.CreatedBy;
                obj.CreatedDate = storageItem.CreatedDate;
                obj.ModifiedBy = storageItem.ModifiedBy;
                obj.ModifiedDate = storageItem.ModifiedDate;
            }
        }

        /// <summary>
        /// Deletes the specified object from the underlying storage
        /// </summary>
        /// <typeparam name="T">The type of the stored object. The type should be inherited from <see cref="StoredObject"/></typeparam>
        /// <param name="obj">The object to delete.</param>
        internal void DeleteObject<T>(T obj) where T: StoredObject
        {
            var serviceContext = this.ServiceContext;
            if (serviceContext == null)
            {
                throw new SPException("Cannot get an instance of the service context associated with the provide site");
            }

            var proxy = AppStorageServiceApplicationProxy.GetProxy(serviceContext);            
            var storageItem = proxy.GetStorageItem(this.Partition, obj.Name);
            if (storageItem != null)
            {
                proxy.DeleteStorageItem(this.Partition, obj.Name);
                obj.Status = StoredObjectStatus.Offline;
            }
            else
            {
                throw new InvalidOperationException("An object of the specified type and name was not found in the underlying storage");
            }
        }

        /// <summary>
        /// Gets the storage partition to retrieve stored items from
        /// </summary>
        private StoragePartition Partition
        {
            get
            {
                var partition = new StoragePartition();

                // depending on the partition context type, we will need to construct
                // the partition object
                switch (this.partitionContext)
                {
                    // the partition represents a SharePoint list
                    case PartitionContextTypes.List:
                        {
                            var listObject = (contextObject as SPList);
                            partition.ListId = listObject.ID;
                            partition.WebId = listObject.ParentWeb.ID;
                            partition.SiteId = listObject.ParentWeb.Site.ID;
                            partition.WebApplicationId = listObject.ParentWeb.Site.WebApplication.Id;

                            break;
                        }
                    // the partition represents a SharePoint site collection
                    case PartitionContextTypes.Site:
                        {
                            var siteObject = (contextObject as SPWeb);
                            partition.WebId = siteObject.ID;
                            partition.SiteId = siteObject.Site.ID;
                            partition.WebApplicationId = siteObject.Site.WebApplication.Id;

                            break;
                        }
                    // the partition represents a SharePoint site collection
                    case PartitionContextTypes.SiteCollection:
                        {
                            var siteObject = (contextObject as SPSite);
                            partition.SiteId = siteObject.ID;
                            partition.WebApplicationId = siteObject.WebApplication.Id;

                            break;
                        }
                    // the partition represents a SharePoint web application
                    case PartitionContextTypes.WebApplication:
                        {
                            partition.WebApplicationId = (contextObject as SPWebApplication).Id;

                            break;
                        }
                }


                return partition;
            }
        }

        /// <summary>
        /// Gets the service context associated with the context site
        /// </summary>
        private SPServiceContext ServiceContext
        {
            get
            {
                SPServiceContext serviceContext = null;
                switch (this.partitionContext)
                {
                    case PartitionContextTypes.WebApplication:
                        {
                            // for web application we will need to go a bit deeper

                            // first fetch the service proxy group
                            var serviceProxyGroup =
                                (this.contextObject as SPWebApplication).ServiceApplicationProxyGroup;
                            if (serviceProxyGroup == null)
                            {
                                serviceProxyGroup = SPServiceApplicationProxyGroup.Default;
                            }

                            if (serviceProxyGroup != null)
                            {
                                // fetch the service context from there
                                serviceContext = SPServiceContext.GetContext(serviceProxyGroup,
                                                                             SPSiteSubscriptionIdentifier.Default);
                            }
                            break;
                        }
                    case PartitionContextTypes.SiteCollection:
                        {
                            serviceContext = SPServiceContext.GetContext((SPSite)this.contextObject);
                            break;
                        }
                    case PartitionContextTypes.Site:
                        {
                            serviceContext = SPServiceContext.GetContext(((SPWeb)contextObject).Site);
                            break;
                        }
                    case PartitionContextTypes.List:
                        {
                            serviceContext = SPServiceContext.GetContext(((SPList)this.contextObject).ParentWeb.Site);
                            break;
                        }

                }

                // No service context? this is an error
                if (serviceContext == null)
                {
                    throw new ArgumentException("A valid service context could not be created for the given context", "contextObject");
                }

                return serviceContext;
            }
        }
    }

    /// <summary>
    /// Enumeration of partition contexts supported by the AppStorage service
    /// </summary>
    enum PartitionContextTypes
    {
        /// <summary>
        /// Represents the WebApplication context
        /// </summary>
        WebApplication,

        /// <summary>
        /// Represents the Site Collection context
        /// </summary>
        SiteCollection,

        /// <summary>
        /// Represents a SharePoint web context
        /// </summary>
        Site,

        /// <summary>
        /// Represents a SharePoint list context
        /// </summary>
        List
    }
}